
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<stdint.h>

#define DBM_FAR_JUMP 1
#define DBM_JUMP 2
#define DBM_NEW_FLAG 3
#define DBM_PRINT_FLAG 4
#define DBM_SET_EXIT 99
#define DBM_EXIT 100

/* enum dbmtype { Flag, Array, Color, Notes, Integer, Float }; */

#define getI(a, b) a = b[0] * 16777216 + b[1] * 65536 + b[2] * 256 + b[3]; b += 4;
#define getF(a, b) a = * ((float *)(b + 4)); b += 4;

#define DEBUG_MODE 0

#if DEBUG_MODE == 1
#define dbm_log(...) printf(__VA_ARGS__)
#else
#define dbm_log(...) ;
#endif

// primitives

struct mem_flag{
  char* symbol;
  int length;
};

struct mem_array{
  void* objects;
  int length;
};

struct mem_color{
  uint8_t r;
  uint8_t g;
  uint8_t b;
  uint8_t a;
};

struct mem_notes{
  uint8_t (*words)[4];
  int length;
} notes;

struct mem_integer{
    int val;
};

struct mem_float{
  float val;
};

struct mem_vector1{
  float val;
};

struct mem_vector2{
  float x;
  float y;
};

struct mem_vector3{
  float x;
  float y;
  float z;
};

struct mem_vector4{
  float x;
  float y;
  float z;
  float w;
};

// stack

typedef void* mem_object;

struct stack_node{
  struct stack_node* prev;
  mem_object mem;
};

// 
  
struct dbm_codepage{
  uint8_t* bin;
  int length;
};

struct dbm_codebook{
  struct dbm_codepage* page;
  int length;
};

struct dbm_codehouse{
  struct dbm_codebook* book;
  int length;
};

struct dbm{
  struct stack_node* top;
  struct dbm_codehouse* space;
  int sc;
  int o1;
  int o2;
  int o3;
  int of;
  int exitcode;
};

/* dbm.space[o1].books[o2].pages[o3]; */

void dbm_init(struct dbm* dbm){
  dbm->top = NULL;
  dbm->space = NULL;
  dbm->sc = 0;
  dbm->o1 = 0;
  dbm->o2 = 0;
  dbm->o3 = 0;
  dbm->of = 0;
  dbm->exitcode = 255;
}

void fatal(const uint8_t* msg){
  fprintf(stderr, msg);
  exit(1);
}

void dbm_loadn(struct dbm* dbm, int o1, int o2, int o3, int binlen, const uint8_t* bin){
  int i, j;
  j = o1 + 1;
  if((dbm->space == NULL) ||
     (dbm->sc < j)){
    dbm->space = realloc(dbm->space, j * sizeof(struct dbm_codehouse));
    if(dbm->space == NULL){
      fatal("Allocate codespace memory failed");
    }
    for(i=dbm->sc; i<j; ++i){
      dbm->space[i].book = NULL;
      dbm->space[i].length = 0;
    }
    dbm->sc = j;
  };
  j = o2 + 1;
  if((dbm->space[o1].book == NULL) ||
     (dbm->space[o1].length < j)){
    dbm->space[o1].book = realloc(dbm->space[o1].book,
				  j * sizeof(struct dbm_codebook));
    if(dbm->space[o1].book == NULL){
      fatal("Allocate codebook memory failed");
    }
    for(i=dbm->space[o1].length; i<j; ++i){
      dbm->space[o1].book[i].page = NULL;
      dbm->space[o1].book[i].length = 0;
    }
    dbm->space[o1].length = j;
  }
  j = o3 + 1;
  if((dbm->space[o1].book[o2].page == NULL) ||
     (dbm->space[o1].book[o2].length < j)){
    dbm->space[o1].book[o2].page = realloc(dbm->space[o1].book[o2].page, j * sizeof(struct dbm_codepage));
    if(dbm->space[o1].book[o2].page == NULL){
      fatal("Allocate codepage memory failed");
    }
    for(i=dbm->space[o1].book[o2].length; i<j; ++i){
      dbm->space[o1].book[o2].page[i].bin = NULL;
      dbm->space[o1].book[o2].page[i].length = 0;
    };
    dbm->space[o1].book[o2].length = j;
  };
  if(dbm->space[o1].book[o2].page[o3].length < binlen){
    free(dbm->space[o1].book[o2].page[o3].bin);
    dbm->space[o1].book[o2].page[o3].bin = malloc(sizeof(uint8_t) * binlen);
    if(dbm->space[o1].book[o2].page[o3].bin == NULL){
      fatal("Allocate codebin memory failed");
    }
    dbm->space[o1].book[o2].page[o3].length = binlen;
  };
  memcpy(dbm->space[o1].book[o2].page[o3].bin, bin, sizeof(uint8_t) * binlen);
}

#define dbm_load(dbm, o1, o2, o3, bin) dbm_loadn(dbm, o1, o2, o3, strlen(bin), bin)

void dbm_print(struct dbm* dbm){
  int i, j, k, l, c, r;
  if(dbm == NULL){
    fprintf(stderr, "Print DBM but Null");
    exit(1);
  }
  printf("sc: %d o1: %d o2: %d o3: %d of: %d\n",
	 dbm->sc, dbm->o1, dbm->o2, dbm->o3, dbm->of);
  for(i=0; i<dbm->sc; ++i){
    if(dbm->space[i].book == NULL){
      printf("space %d is empty.\n\n", i);
      continue;
    }
    for(j=0; j<dbm->space[i].length; ++j){
      if(dbm->space[i].book[j].page == NULL){
	printf("space %d book %d is empty.\n\n", i, j);
	continue;
      }
      for(k=0; k<dbm->space[i].book[j].length; ++k){
	if(dbm->space[i].book[j].page[k].bin == NULL){
	  printf("space %d book %d page %d is empty.\n\n", i, j, k);
	}
	printf("space %d book %d page %d :\n", i, j, k);
	for(l=0, c=0, r=0; l<dbm->space[i].book[j].page[k].length; ++l){
	  printf("%3d ", dbm->space[i].book[j].page[k].bin[l]);
	  ++c;
	  if(c == 4){
	    printf("| ");
	    ++r;
	    c = 0;
	  }
	  if(r == 5){
	    printf("\n");
	    r = 0;
	  }
	}
	printf("\n");
      }
    }
  }	      
}

void dbm_setjump(struct dbm* dbm, int o1, int o2, int o3, int of){
  if(dbm == NULL){
    fatal("Setjump on NULL");
  }
  dbm->o1 = o1;
  dbm->o2 = o2;
  dbm->o3 = o3;
  dbm->of = of;
}

void dbm_act(struct dbm* dbm){
  uint8_t *start, *maxof, *of;
  uint8_t ti, tl;
  struct mem_flag *tf;
  struct stack_node* tsn0;
  int i;
  start = dbm->space[dbm->o1].book[dbm->o2].page[dbm->o3].bin;
  maxof = start + dbm->space[dbm->o1].book[dbm->o2].page[dbm->o3].length;
  of = start;
  while(of < maxof){
    dbm_log("pos %d\n", of - start);
    switch(*of){

      //
    case DBM_FAR_JUMP:
      dbm_log("case DBM_FAR_JUMP\n");
      dbm->o1 = ((*(of+1)) * 16777216 +
		(*(of+2)) * 65536 +
		(*(of+3)) * 256 +
		(*(of+4)));
      dbm->o2 = ((*(of+5)) * 16777216 +
		(*(of+6)) * 65536 +
		(*(of+7)) * 256 +
		(*(of+8)));
      dbm->o3 = ((*(of+9)) * 16777216 +
		(*(of+10)) * 65536 +
		(*(of+11)) * 256 +
		(*(of+12)));
      start = dbm->space[dbm->o1].book[dbm->o2].page[dbm->o3].bin;
      of = start + ((*(of+13)) * 16777216 +
		    (*(of+14)) * 65536 +
		    (*(of+15)) * 256 +
		    (*(of+16)));
      maxof = of + dbm->space[dbm->o1].book[dbm->o2].page[dbm->o3].length;
      break;

      //
    case DBM_JUMP:
      dbm_log("case DBM_JUMP\n");
      dbm_log("var %d", (*(of+4)));
      of = start + ((*(int *)(of+1)) * 16777216 +
		    (*(of+2)) * 65536 +
		    (*(of+3)) * 256 +
		    (*(of+4)));
      break;

      //
    case DBM_NEW_FLAG:
      dbm_log("case DBM_NEW_FLAG\n");
      tl = *(of+1);
      tf = malloc(sizeof(struct mem_flag));
      tf->symbol = malloc(sizeof(char) * (tl+1));
      of += 2;
      for(ti=0; ti<tl; ++ti){
	tf->symbol[ti] = of[ti];
      };
      tf->symbol[ti+1] = '\0';
      tf->length = tl;
      tsn0 = malloc(sizeof(struct stack_node));
      tsn0->prev = dbm->top;
      tsn0->mem = (void*) tf;
      dbm->top = tsn0;
      of += tl;
      break;

      //
    case DBM_PRINT_FLAG:      
      dbm_log("case DBM_PRINT_FLAG\n");
      tf = (struct mem_flag *) dbm->top->mem;
      fprintf(stdout, "%s", tf->symbol);
      ++of;
      break;

      //
    case DBM_SET_EXIT:
      dbm_log("case DBM_SET_EXIT\n");
      ++of;
      dbm->exitcode = *(of);
      ++of;
      break;

      //
    case DBM_EXIT:
      dbm_log("case DBM_EXIT\n");
      ++of;
      dbm->exitcode = 0;
      return;
      break;

      //
    default:
      fatal("Unknown opcode");
    }
  }
  fatal("No halt after page over");
}

int test0(){
  struct dbm* dbm1 = malloc(sizeof(struct dbm));
  char code[] = { 3, 12, 'H','e','l','l','o',',','W','o','r','l','d','\n', 4, 99, 0, 100, 0 };
  dbm_init(dbm1);
  dbm_loadn(dbm1, 0, 0, 0, sizeof(code), code);
  dbm_setjump(dbm1, 0, 0, 0, 0);
  dbm_print(dbm1);
  dbm_act(dbm1);
  printf("exit code: %d\n", dbm1->exitcode);
  return dbm1->exitcode;
}

int test1(){
  struct dbm* dbm1 = malloc(sizeof(struct dbm));
  char code[] = { 3, 12, 'H','e','l','l','o',',','W','o','r','l','d','\n', 4, 4, 99, 0, 100, 0 };
  dbm_init(dbm1);
  dbm_loadn(dbm1, 0, 0, 0, sizeof(code), code);
  dbm_setjump(dbm1, 0, 0, 0, 0);
  dbm_print(dbm1);
  dbm_act(dbm1);
  printf("exit code: %d\n", dbm1->exitcode);
  return dbm1->exitcode;
}

int test2(){
  struct dbm* dbm1 = malloc(sizeof(struct dbm));
  char code[] = { 2, 0, 0, 0, 10, 3, 3, 'b', 'b', 'b', 3, 12, 'H','e','l','l','o',',','W','o','r','l','d','\n', 4, 4, 99, 5, 100, 0 };
  dbm_init(dbm1);
  dbm_loadn(dbm1, 0, 0, 0, sizeof(code), code);
  dbm_setjump(dbm1, 0, 0, 0, 0);
  dbm_print(dbm1);
  dbm_act(dbm1);
  printf("exit code: %d\n", dbm1->exitcode);
  return dbm1->exitcode;
}

int main(int argc, char** argv){
  return test0();
}
